package com.cloudfast.oauth.config;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;

import com.cloudfast.context.CloudSecurity;
import com.cloudfast.oauth.client.CustomClientCredentialsTokenEndpointFilter;
import com.cloudfast.oauth.feign.UserCenterFeignService;
import com.cloudfast.oauth.granter.CaptchaTokenGranter;
import com.cloudfast.oauth.granter.DingTokenGranter;
import com.cloudfast.oauth.granter.SmsTokenGranter;
import com.cloudfast.oauth.password.CloudPasswordEncoder;
import com.cloudfast.oauth.service.CustomUserDetailsService;
import com.cloudfast.oauth.token.CloudTokenServices;
import com.cloudfast.oauth.token.CloudTokenStore;
import com.cloudfast.oauth.translator.ResponseExceptionTranslator;
import com.cloudfast.redis.CloudRedisTemplate;

/**
 * 认证管理识别器
 *
 * @author liuyw
 * @date 2022年8月29日
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    /**
     * 安全验证
     */
    @Autowired
    private CloudSecurity cloudSecurity;

    @Autowired
    private CloudRedisTemplate cloudRedisTemplate;

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Autowired
    private UserCenterFeignService userFeignService;

    /**
     * 密码
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder cloudPasswordEncoder() {
        return new CloudPasswordEncoder();
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        CustomClientCredentialsTokenEndpointFilter endpointFilter = new CustomClientCredentialsTokenEndpointFilter(
                security);
        endpointFilter.afterPropertiesSet();
        endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        security.addTokenEndpointAuthenticationFilter(endpointFilter);
        // 注意：security不需要在调用allowFormAuthenticationForClients方法
        security.authenticationEntryPoint(authenticationEntryPoint);
        security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");

    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 数据库模式
        clients.withClientDetails(clientDetails());

    }

    @Bean
    ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 自定义的授权模式
     *
     * @param endpoints
     * @return
     */
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        // 默认tokenGranter集合
        List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
        // 增加短信验证码模式
        granters.add(new SmsTokenGranter(authenticationManager, endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), cloudRedisTemplate,
                userDetailsService));
        // 添加 captcha验证模式
        granters.add(new CaptchaTokenGranter(authenticationManager, endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), cloudRedisTemplate,
                userDetailsService, cloudPasswordEncoder(), cloudSecurity));
        granters.add(new DingTokenGranter(authenticationManager, endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), userDetailsService,
                userFeignService));

        // 组合tokenGranter集合
        return new CompositeTokenGranter(granters);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                /**
                 * 自定义授权类型
                 */
                .tokenGranter(tokenGranter(endpoints))

                /**
                 * 认证 只支持 post 方式请求
                 */
                .allowedTokenEndpointRequestMethods(HttpMethod.POST)
                /**
                 * * refresh_token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
                 * 1.重复使用：access_token过期刷新时， refresh token过期时间未改变，仍以初次生成的时间为准
                 * 2.非重复使用：access_token过期刷新时， refresh_token过期时间延续，在refresh_token有效期内刷新而无需失效再次登录
                 */
                .reuseRefreshTokens(false)
                /**
                 * 认证异常类处理
                 */
                .exceptionTranslator(new ResponseExceptionTranslator())
                /**
                 * 默认 token 生成
                 */
                .tokenServices(defaultTokenServices());
    }

    /**
     * <p>
     * 注意，自定义TokenServices的时候，需要设置@Primary，否则报错，
     * </p>
     *
     * @return
     */
    @Primary
    @Bean
    CloudTokenServices defaultTokenServices() {
        CloudTokenServices tokenServices = new CloudTokenServices(cloudSecurity.isSingleLogin());
        tokenServices.setTokenStore(new CloudTokenStore(redisConnectionFactory,clientDetails(), cloudSecurity));
        // 支持刷新token
        tokenServices.setSupportRefreshToken(true);
        // token有效期自定义设置
        tokenServices.setAccessTokenValiditySeconds(60 * cloudSecurity.getExpiresTokenTime());
        // refresh_token默认30天
        tokenServices.setRefreshTokenValiditySeconds(60 * cloudSecurity.getRefreshTokenTime());
        return tokenServices;
    }
}